package com.leyou.search.servcie;

import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.dto.*;
import com.leyou.item.client.ItemClient;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.index.Term;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Service
public class SearchService {
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private GoodsRepository goodsRepository;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    public Goods buildGoods(SpuDTO spu) {
        Long spuId = spu.getId();
        //1.搜索字段，包含标题、分类、品牌
        //1.1查询分类
        String categoryNames = spu.getCategoryName();
        if (StringUtils.isBlank(categoryNames)) {
            itemClient.queryCategoryByIds(spu.getCategoryIds())
                    .stream()
                    .map(CategoryDTO::getName)
                    .collect(Collectors.joining());
        }
        //1.2 查询品牌
        String brandName = spu.getBrandName();
        if (StringUtils.isBlank(brandName)) {
            brandName = itemClient.queryBrandById(spu.getBrandId()).getName();
        }
        //1.3拼接
        String all = spu.getName() + categoryNames + brandName;

        //2. spu下的sku的集合
        //2.1 查询sku
        List<SkuDTO> skuList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuList)) {
            skuList = itemClient.querySkuBySpuId(spuId);
        }
        //2.2.处理sku的字段
        List<Map<String, Object>> skus = new ArrayList<>();
        //2.3准备价格的集合
        Set<Long> price = new TreeSet<>();

        for (SkuDTO sku : skuList) {
            //处理sku的字段
            Map<String, Object> map = new HashMap<>();
            map.put("id", sku.getId());
            map.put("image", StringUtils.substringBefore(sku.getImages(), ","));
            map.put("price", sku.getPrice());
            map.put("title", sku.getTitle());
            skus.add(map);
            //处理价格
            price.add(sku.getPrice());
        }
        //4.规格参数,key:规格参数名称，value:规格参数值
        HashMap<String, Object> specs = new HashMap<>();
        //4.1插询规格参数名称，作为key。查询当前分类下，可以搜索的规格参数
        List<SpecParamDTO> params = itemClient.querySpecParams(null, spu.getCid3(), true);
        //4.2查询spuDetail,获取规格参数的值
        SpuDetailDTO detail = spu.getSpuDetail();
        if (detail == null) {
            detail = itemClient.querySpuDetailById(spuId);
        }
        //4.2.1取出通用规格参数值
        String json = detail.getGenericSpec();
        Map<Long, Object> genericSpec = JsonUtils.toMap(json, Long.class, Object.class);
        //4.2.2取出特有规格参数的值
        json = detail.getSpecialSpec();
        Map<Long, Object> specialSpec = JsonUtils.toMap(json, Long.class, Object.class);
        //4.2.3 合并规格值
        genericSpec.putAll(specialSpec);
        //4.3把key和value配对，存入specs中
        for (SpecParamDTO param : params) {
            //取出规格参数名称,作为map的key
            String key = param.getName();
            //根据param的id获取值，作为specs中的value
            Object value = genericSpec.get(param.getId());
            //判断是否为数字
            if (param.getNumeric()) {
                //是数字,取出segments，判断当前value在哪个segment(区间),用直观segment代替value
                value = chooseSegment(value, param);

            }
            //存入specs
            specs.put(key, value);

        }
        //创建goods
        Goods goods = new Goods();
        goods.setSubTitle(spu.getSubTitle());
        //查询规格参数的key和value
        goods.setSpecs(specs);
        //查询spu下的sku
        goods.setSkus(JsonUtils.toString(skuList));
        //查询sku的价格集合
        goods.setPrice(price);
        goods.setCreateTime(new Date().getTime());
        goods.setCategory(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        //搜索字段,包含名称、分类、品牌等数据
        goods.setAll(all);
        goods.setId(spu.getId());
        return goods;


    }

    private Object chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = 0;
        try {
            val = parseDouble(value.toString());
        } catch (Exception e) {
            return value;
        }
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        return Double.parseDouble(str);
    }

    public PageResult<GoodsDTO> search(SearchRequest request) {
        //0.健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        //1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.组织条件
        //2.0s.source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        //2.1搜索条件
        QueryBuilder queryBuilder1 = buildBasicQuery(request);
        queryBuilder.withQuery(queryBuilder1);
        //2.2分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));
        //3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //4.解析结果
        //4.1解析分页数据
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> list = result.getContent();
        //4.2转换DTO
        List<GoodsDTO> dtoList = BeanHelper.copyWithCollection(list, GoodsDTO.class);
        //5.封装并返回
        return new PageResult<>(total, totalPage, dtoList);
    }

    public Map<String, List<?>> queryFilters(SearchRequest request) {
        //1.创建过滤项集合
        LinkedHashMap<String, List<?>> filterList = new LinkedHashMap<>();
        //2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1获取查询条件
        if (StringUtils.isBlank(request.getKey())) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        //2.2.减少查询结果(这里只需要聚合结果)
        //每页显示一个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, new String[0]));
        //3.聚合条件
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("category"));
        //4.发起请求，查询数据
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //5.解析聚合结果
        Aggregations aggregations = result.getAggregations();
        //5.1处理分类的聚合
        //1)根据聚合名称，获取聚合结果
        Terms categoryAggTerm = aggregations.get("categoryAgg");
        //2)获取桶，解析桶中的数据
        List<Long> categoryIdList = getIdListFromBuckets(categoryAggTerm);
        //3)根据id集合,查询分类的集合
        List<CategoryDTO> categoryList = itemClient.queryCategoryByIds(categoryIdList);
        //4)放入filter
        filterList.put("分类", categoryList);

        //7.2处理品牌的聚合
        //1)根据聚合名称，获取聚合结果
        Terms brandAggTerm = aggregations.get("brandAgg");
        //2)获取桶，解析桶中的数据
        List<Long> brandIdList = getIdListFromBuckets(brandAggTerm);
        //3)根据品牌id集合,查询品牌的集合
        List<BrandDTO> brandList = itemClient.queryBrandByIds(brandIdList);
        //4)放入filter
        filterList.put("品牌", brandList);

        //8.判断是否需要对规格参数聚合，只有分类只剩下一个，才聚合
        if (categoryIdList.size() == 1) {
            handleSpecAggregation(categoryIdList.get(0),request,filterList);
        }

        return filterList;
    }

    /*
     * 构建基本的match查询
     * */
    private QueryBuilder buildBasicQuery(SearchRequest request) {
        //构建布尔查询
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //构建基本的match查询
        queryBuilder.must(QueryBuilders.matchQuery("all",request.getKey()).operator(Operator.AND));
        //构建过滤条件
        Map<String, String> filter = request.getFilter();
        if(!CollectionUtils.isEmpty(filter)){
            for (Map.Entry<String, String> entry : filter.entrySet()) {
                //获取过滤条件的key
                String key = entry.getKey();
                //规格参数的key要做前缀specs.
                if("分类".equals(key)){
                    key="category";
                }else if ("品牌".equals(key)){
                    key="brandId";
                }else {
                    key="specs."+key;
                }
                //value
                String value = entry.getValue();
                //添加过滤条件
                queryBuilder.filter(QueryBuilders.termQuery(key,value));
            }

        }



        return queryBuilder;
    }

    /*
     * 根据聚合结果获得聚合中桶中的key的集合
     * */
    private List<Long> getIdListFromBuckets(Terms terms) {
       /*传统方式
       List<? extends Terms.Bucket> buckets = terms.getBuckets();
        List<Long> idList=new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            long id = bucket.getKeyAsNumber().longValue();
            idList.add(id);
        }
        return idList;*/


        //流方式
        return terms.getBuckets()
                .stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
    }

    /*
     * 当分类只有一个时，对规格聚合
     * */
    private void handleSpecAggregation(Long id, SearchRequest request, Map<String, List<?>> filters) {
        //先根据分类查询searching为true的规格
        List<SpecParamDTO> params = itemClient.querySpecParams(null, id, true);
        //2.准备查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //2.1.source过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, new String[0]));
        //2.2搜索条件
        if (StringUtils.isBlank(request.getKey())) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        QueryBuilder basicQuery = buildBasicQuery(request);
        queryBuilder.withQuery(basicQuery);
        //2.3分页
        queryBuilder.withPageable(PageRequest.of(0, 1));
        //3.准备聚合条件
        for (SpecParamDTO param : params) {
            //取出参数名称,作为聚合的名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }
        //4.发起请求
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //获取聚合结果
        Aggregations aggregations = result.getAggregations();
        //解析聚合结果
        for (SpecParamDTO param : params) {
            //取出参数名称，作为聚合的名称
            String name = param.getName();
            Terms terms = aggregations.get(name);
            //冲聚合结果中获取桶，解析桶中的数据
            List<String> list = terms.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            //将结果放到map中
            filters.put(name,list);
        }


    }
    /*
    * 在索引库中新增索引
    * */
    public void createIndex(Long id) {
        //查询spu
        SpuDTO spu = itemClient.querySpuById(id);
        //构建成goods对象
        Goods goods = buildGoods(spu);
        //保存数据到索引库
        goodsRepository.save(goods);
    }

    public void deleteById(Long id) {
        goodsRepository.deleteById(id);
    }
}
